<?php

namespace Fluency\Utils;

use Fluency\Utils\HeadTailList;

/**
 * A derivation of IteratorIterator that applies a given transformation to each element of a given
 * iterator (or an array). It also supports chained transformations for multi-dimensional data
 * structures.
 *
 * @package Fluency\Utils
 * @author  Ignas Rudaitis
 * @since   1.0
 */
class TransformingIterator extends \IteratorIterator implements \Countable
{
    private $transformationChain;

    /**
     * The constructor. It takes an iterator (or an array) to decorate and a chain of
     * transformations to be applied to the different dimensions of the iterator (array).
     *
     * @param mixed $iterator
     * @param Fluency\Utils\HeadTailList $transformationChain
     */
    public function __construct($iterator, HeadTailList $transformationChain)
    {
        $iterator = $this->ensureTraversable($iterator);
        $this->transformationChain = $transformationChain;
        parent::__construct($iterator);
    }

    /**
     * Returns the current value of the underlying iterator with the transformation applied.
     *
     * @see Iterator::current()
     *
     * @return mixed
     */
    public function current()
    {
        $value = parent::current();
        if ($this->transformationChain->hasTail() && $this->canDecorate($value)) {
            $value = new TransformingIterator($value, $this->transformationChain->getTail());
        }
        $transformation = $this->transformationChain->getHead();
        return $transformation($value);
    }

    private function canDecorate($value)
    {
        return is_array($value) || $value instanceof \Traversable;
    }

    private function ensureTraversable($value)
    {
        if ($value instanceof \Traversable) {
            return $value;
        } elseif (is_array($value)) {
            return new \ArrayIterator($value);
        } else {
            throw new \InvalidArgumentException(
                    __CLASS__ . "::__construct() expects either an array or an instance of " .
                    "Traversable as the first argument");
        }
    }

    /**
     * Counts the elements in the underlying iterator using the count() function of PHP.
     *
     * @see Countable::count()
     *
     * @return integer
     */
    public function count()
    {
        return count($this->getInnerIterator());
    }
}
